using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace Framework
{
    public static class FileUtil
    {
        public enum CopyDirectoryLog
        {
            None,

            PrintCopiedFileName
        }

        #region Notes
        /*
               //Logger.Debug( "AppDomain.CurrentDomain.BaseDirectory: " + AppDomain.CurrentDomain.BaseDirectory );
               //Logger.Debug( "AppDomain.CurrentDomain.DynamicDirectory: " + AppDomain.CurrentDomain.DynamicDirectory );
               //Logger.Debug( "AppDomain.CurrentDomain.FriendlyName: " + AppDomain.CurrentDomain.FriendlyName );
               //Logger.Debug( ": " + ads.ApplicationBase );
               //Logger.Debug( ": " + ads.ApplicationName );
               //Logger.Debug( ": " + ads.CachePath );
               //Logger.Debug( ": " + ads.ConfigurationFile );
               //Logger.Debug( ": " + ads.DynamicBase );
               //Logger.Debug( "System.Environment.CurrentDirectory: " + System.Environment.CurrentDirectory );
               //Logger.Debug( "Assembly.GetExecutingAssembly().Location: " + System.Reflection.Assembly.GetExecutingAssembly().Location );
               //Logger.Debug( "GetDirectoryName(GetExecutingAssembly().Location): " + System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) );
      */
        #endregion

        public const int MAXPATH = 255;

        #region ApplicationBaseDirectory
        /// <summary>Get Base Application Directory</summary>
        /// 
        /// <returns>Base directory</returns>
        public static string ApplicationBaseDirectory()
        {
            return AppDomain.CurrentDomain.BaseDirectory;
        }
        #endregion

        #region Write
        /// <summary>Write contents to text file, default action is Create</summary>
        /// 
        /// <param name="fileName">The name of the file to write to</param>
        /// <param name="content">The content to write out to the file</param>
        public static void Write(string fileName, string content)
        {
            Write(fileName, content, true, OnException.Throw);
        }

        /// <summary>Write contents to text file.</summary>
        /// <param name="fileName">The name of the file to write to</param>
        /// <param name="content">The content to write out to the file</param>
        /// <param name="overwrite">Action to take if file exists</param>
        public static void Write(string fileName, string content, bool overwrite)
        {
            Write(fileName, content, overwrite, OnException.Throw);
        }

        /// <summary>Write contents to text file</summary>
        /// 
        /// <param name="fileName">The name of the file to write to</param>
        /// <param name="content">The content to write out to the file</param>
        /// <param name="overwrite">Action to take if file exists</param>
        /// <param name="onException"></param>
        public static void Write(string fileName, string content, bool overwrite, OnException onException)
        {
            try
            {
                if (overwrite)
                {
                    File.WriteAllText(fileName, content);
                }
                else
                {
                    File.AppendAllText(fileName, content);
                }
            }
            catch (Exception)
            {
                // David: Handle Exception
                // LogUtil.Write("FileUtil.WriteTo: " + fileName + "\n" + ex.Message);

                if (onException == OnException.Throw)
                {
                    throw;
                }
            }
        }

        /// <summary>Write data to text file, default action is Create</summary>
        /// 
        /// <param name="fileName">The name of the file to write to</param>
        /// <param name="data">The data to write out to the file</param>
        public static void Write(string fileName, byte[] data)
        {
            Write(fileName, data, true, OnException.Throw);
        }

        /// <summary>Write data to text file.</summary>
        /// <param name="fileName">The name of the file to write to</param>
        /// <param name="data">The data to write out to the file</param>
        /// <param name="overwrite">Action to take if file exists</param>
        /// <param name="overwrite"></param>
        public static void Write(string fileName, byte[] data, bool overwrite)
        {
            Write(fileName, data, overwrite, OnException.Throw);
        }

        /// <summary>Write data to text file</summary>
        /// 
        /// <param name="fileName">The name of the file to write to</param>
        /// <param name="data">The data to write out to the file</param>
        /// <param name="overwrite">Action to take if file exists</param>
        /// <param name="onException"></param>
        public static void Write(string fileName, byte[] data, bool overwrite, OnException onException)
        {
            try
            {
                if (overwrite)
                {
                    using (FileStream stream = File.Create(fileName))
                    {
                        stream.Write(data, 0, data.Length);
                    }
                }
                else
                {
                    using (FileStream stream = File.OpenWrite(fileName))
                    {
                        stream.Write(data, 0, data.Length);
                    }
                }
            }
            catch (Exception)
            {
                // David: Handle Exception
                // LogUtil.Write("FileUtil.WriteTo: " + fileName + "\n" + ex.Message);

                if (onException == OnException.Throw)
                {
                    throw;
                }
            }
        }
        #endregion

        #region Read
        /// <summary>
        /// Read contents of text file. (Ignores Exceptions)<br /> <br />
        /// 
        /// The default ReadAllText logs exceptions and returns an empty
        /// string on exception.
        /// </summary>
        /// <param name="fileName">The name of the file to read</param>
        /// <returns>Contents of the file</returns>
        public static string Read(string fileName)
        {
            return Read(fileName, OnException.Throw);
        }

        /// <summary>
        /// Read contents of text file. <br /> <br />
        /// 
        /// The default ReadAllText logs exceptions and returns an empty
        /// string on exception.
        /// </summary>
        /// <param name="fileName">The name of the file to read</param>
        /// <param name="onException">Exception may be ignored or re-thrown</param>
        /// <returns>Contents of the file</returns>
        public static string Read(string fileName, OnException onException)
        {
            var result = String.Empty;

            try
            {
                result = File.ReadAllText(fileName);
            }
            catch // (Exception ex)
            {
                // David: Handle Exception
                // LogUtil.Write("FileUtil.WriteTo: " + fileName + "\n" + ex.Message);

                if (onException == OnException.Throw)
                {
                    throw;
                }
            }

            return result;
        }
        #endregion

        #region ReadRetryOnFail
        /// <summary>
        /// Attempt to read in a text file and 
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="numberOfAttempts"></param>
        /// <returns></returns>
        public static string ReadRetryOnFail(string fileName, int noOfTries, OnException onException)
        {
            TryUntilSuccessEvent ee = ExceptionUtil.TryUntilSuccess(noOfTries, 100, new TryUntilSuccessDelegate(ReadRetryOnFail), fileName);

            if (ee.Success)
            {
                return (string)ee.Result;
            }

            //Logger.Error("FileUtil.ReadTextFileRetryOnFail failed to read: " + fileName + "\n", ee.ThrownException);

            if (onException == OnException.Throw)
            {
                throw ee.ThrownException;
            }

            return String.Empty;
        }

        private static void ReadRetryOnFail(TryUntilSuccessEvent e)
        {
            try
            {
                StreamReader sr = File.OpenText((string)e.Param);

                e.Result = sr.ReadToEnd();

                sr.Close();

                e.Success = true;
            }
            catch (Exception ex)
            {
                e.ThrownException = ex;
            }
        }
        #endregion

        #region PathCombineAsDirectory
        /// <summary>
        /// Combine multiple file parts into a single path statement
        /// </summary>
        /// <param name="fileParts"></param>
        /// <returns></returns>
        public static string PathCombineAsDirectory(params string[] fileParts)
        {
            string result = PathCombine(fileParts);

            return (result.Length > 1 && result[result.Length - 1] == Path.DirectorySeparatorChar ? result : result + Path.DirectorySeparatorChar);
        }
        #endregion

        #region PathCombine
        /// <summary>Combines two path strings</summary>
        /// <returns>A string containing the combined paths. If one of the specified paths is a zero-length string, this method returns the other path. If path2 contains an absolute path, this method returns path2.</returns>
        /// <remarks>This code is the same as the FileUtil.PathCombine() except a minor bug is fixed when a Volume and Rooted Path are joined </remarks>
        /// <param name="path2">The second path. </param>
        /// <param name="path1">The first path. </param>
        /// <exception cref="T:System.ArgumentNullException">path1 or path2 is null. </exception>
        /// <exception cref="T:System.ArgumentException">path1 or path2 contain one or more of the invalid characters defined in <see cref="F:System.IO.Path.InvalidPathChars"></see>, or contains a wildcard character. </exception>
        /// <filterpriority>1</filterpriority>
        public static string PathCombine(string path1, string path2)
        {
            string result = String.Empty;

            if (CheckInvalidPathChars(path1) || CheckInvalidPathChars(path2)) throw new ArgumentException("Invalid Path Characters");

            if (Is.EmptyString(path2))
            {
                return path1;
            }
            if (Is.EmptyString(path1))
            {
                return path2;
            }

            //if (Path.IsPathRooted(path2))
            //{// Minor modificate to fix Volume seperated rooted directories
            //    return ( path1[path1.Length - 1] == Path.VolumeSeparatorChar ? path1+path2 : path2);
            //}
            char ch1 = path1[path1.Length - 1];

            char ch2 = path2[0];

            if (
                (ch1 == Path.DirectorySeparatorChar && ch2 == Path.DirectorySeparatorChar) ||
                (ch1 == Path.AltDirectorySeparatorChar && ch2 == Path.AltDirectorySeparatorChar)
                )
            {
                return path1 + path2.Substring(1);
            }

            if (
                ch1 == Path.DirectorySeparatorChar ||
                ch1 == Path.AltDirectorySeparatorChar ||
                ch1 == Path.VolumeSeparatorChar ||
                ch2 == Path.DirectorySeparatorChar ||
                ch2 == Path.AltDirectorySeparatorChar)
            {
                return path1 + path2;
            }

            return path1 + Path.DirectorySeparatorChar + path2;
        }

        /// <summary>
        /// Combine multiple file parts into a single path statement
        /// </summary>
        /// <param name="fileParts"></param>
        /// <returns></returns>
        public static string PathCombine(params string[] fileParts)
        {
            string result = String.Empty;

            foreach (string filePart in fileParts)
            {
                result = PathCombine(result, filePart);
            }

            return result;
        }
        #endregion

        #region DirectoryExists
        public static bool DirectoryExists(string path)
        {
            var dir = new DirectoryInfo(path);

            return dir.Exists;
        }
        #endregion

        #region CreateDirectoryIfNotExist
        public static void CreateDirectoryIfNotExist(string path)
        {
            CreateDirectoryIfNotExist(path, true);
        }
        /// <summary>
        /// Directory name or fully pathed file name where the directory resides
        /// </summary>
        /// <param name="path"></param>
        /// <param name="isPathDirectory"></param>
        public static void CreateDirectoryIfNotExist(string path, bool isPathDirectory)
        {
            var file = new FileInfo(path);

            if (file.Directory != null && !file.Directory.Exists)
            {
                file.Directory.Create();
            }

            if (isPathDirectory)
            {
                var dir = new DirectoryInfo(path);

                if (!dir.Exists)
                {
                    dir.Create();
                }
            }
        }
        #endregion

        #region GetFileName
        /// <summary>
        /// Gets the file name from a full folder/file path.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        public static string GetFileName(string fileName)
        {
            return Path.GetFileName(fileName);
        }
        #endregion

        #region GetFileNameWithoutExtension
        /// <summary>
        /// Gets the file name without extension.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        public static string GetFileNameWithoutExtension(string fileName)
        {
            return Path.GetFileNameWithoutExtension(fileName);
        }
        #endregion

        #region GetFullFileNameWithoutExtension
        /// <summary>
        /// Gets the full file name without extension.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        public static string GetFullFileNameWithoutExtension(string fileName)
        {
            var file = new FileInfo(fileName);

            var path = file.DirectoryName;
            var fileWithoutExtension = GetFileNameWithoutExtension(fileName);

            return PathCombine(path, fileWithoutExtension);
        }
        #endregion

        #region GetExtension
        /// <summary>
        /// Gets the file name extension.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        public static string GetExtension(string fileName)
        {
            return Path.GetExtension(fileName);
        }
        #endregion

        #region SetFileNameExtension
        /// <summary>
        /// Sets the file name with a new extension.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="extension">The extension.</param>
        /// <returns></returns>
        public static string SetFileNameExtension(string fileName, string extension)
        {
            fileName = GetFileNameWithoutExtension(fileName);

            return "{0}{1}{2}".FormatWith(fileName, (StrUtil.StartsWith(extension, ".") ? string.Empty : "."), extension);
        }

        /// <summary>
        /// Sets the file name with a new extension.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="oldExtension">The old extension.</param>
        /// <param name="newExtension">The new extension.</param>
        /// <returns></returns>
        public static string SetFileNameExtension(string fileName, string oldExtension, string newExtension)
        {
            fileName = RemoveExtension(fileName, oldExtension);

            return "{0}{1}{2}".FormatWith(fileName, (StrUtil.StartsWith(newExtension, ".") ? string.Empty : "."), newExtension);
        }
        #endregion

        #region GetDirectoryName
        /// <summary>
        /// Gets the name of the directory.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        public static string GetDirectoryName(string fileName)
        {
            return GetDirectoryName(fileName, OnException.Throw);
        }
        /// <summary>
        /// Get Directory Name
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="onException">The on exception.</param>
        /// <returns></returns>
        public static string GetDirectoryName(string fileName, OnException onException)
        {
            try
            {
                return Path.GetDirectoryName(fileName);
            }
            catch
            {
                if (onException == OnException.Ignore)
                {
                    return FileUtil.ApplicationBaseDirectory();
                }
                throw;
            }
        }
        #endregion

        #region SetFileDate
        /// <summary>
        /// Change the date/time stamp on a file
        /// </summary>
        /// <param name="fileInfo"></param>
        /// <param name="dateTime"></param>
        /// <param name="fileTouchTypes"></param>
        public static void SetFileDate(FileInfo fileInfo, DateTime dateTime, FileTouchType fileTouchTypes)
        {
            try
            {
                bool isAll = (fileTouchTypes & FileTouchType.SetAllFileDateTimes) != 0;

                if ((isAll) || ((fileTouchTypes & FileTouchType.SetCreationTime) != 0))
                {
                    fileInfo.CreationTime = dateTime;
                }

                if ((isAll) || ((fileTouchTypes & FileTouchType.SetLastAccessTime) != 0))
                {
                    fileInfo.LastAccessTime = dateTime;
                }

                if ((isAll) || ((fileTouchTypes & FileTouchType.SetLastWriteTime) != 0))
                {
                    fileInfo.LastWriteTime = dateTime;
                }
            }
            catch (Exception)
            {
                throw;
            }
        }
        #endregion

        #region StartProcess
        public static void StartProcess(string fileName)
        {
            //Logger.Info("Start Process");
            //Logger.Info("File Name: " + fileName);

            Process.Start(fileName);
        }

        //public static void StartProcess(string fileName, string arguments)
        //{
        //    //Logger.Debug("Start Process - File Name: " + fileName + ", Arguments: " + arguments);
        //    Process.Start(fileName, arguments);
        //}

        public static void StartProcess(string fileName, params string[] arguments)
        {
            string args = DelimitedUtil.Combine(arguments, " ");

            //Logger.Info("Start Process");
            //Logger.Info("File Name: " + fileName);
            //Logger.Info("Arguments: " + args);

            Process.Start(fileName, args.ToString());
        }
        #endregion

        #region TouchFile
        /// <summary>
        /// Touch the date/time stamp of a file without altering its contents
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="dateTime"></param>
        /// <param name="fileTouchTypes"></param>
        public static void TouchFile(string fileName, DateTime dateTime, FileTouchType fileTouchTypes)
        {
            TouchFile(new FileInfo(fileName), dateTime, fileTouchTypes, OnException.Throw);
        }
        public static void TouchFile(string fileName, DateTime dateTime)
        {
            TouchFile(new FileInfo(fileName), dateTime,
               FileTouchType.SetLastAccessTime | FileTouchType.SetLastWriteTime, OnException.Throw);
        }
        public static void TouchFile(string fileName)
        {
            TouchFile(new FileInfo(fileName), DateTime.Now,
               FileTouchType.SetLastAccessTime | FileTouchType.SetLastWriteTime, OnException.Throw);
        }
        public static void TouchFile(FileInfo fileInfo, DateTime dateTime, FileTouchType fileTouchTypes, OnException onException)
        {
            try
            {
                SetFileDate(fileInfo, dateTime, fileTouchTypes);
            }
            catch (Exception)
            {
                if (onException == OnException.Throw)
                {
                    throw;
                }
            }
        }
        #endregion

        #region IsFileNewer
        public static bool IsFileNewer(string targetFileName, string compareAgainstFileName)
        {
            var fsTargetFileName = new FileInfo(targetFileName);
            var fsCompareAgainstFileName = new FileInfo(compareAgainstFileName);

            // If our target file does not exist then it cannot be newer
            if (!fsTargetFileName.Exists)
            {
                return false;
            }
            // If our compare against file does not exist then must be newer
            if (!fsCompareAgainstFileName.Exists)
            {
                return true;
            }

            return fsTargetFileName.LastWriteTime >= fsCompareAgainstFileName.LastWriteTime;
        }
        #endregion

        #region TempPath
        /// <summary>
        /// Returns the temporary directory path.
        /// </summary>
        /// <returns></returns>
        public static string TempPath()
        {
            return TempPath(String.Empty);
        }
        /// <summary>
        /// Returns the temporary plus sub-directory path.
        /// </summary>
        /// <param name="subPath">The sub path.</param>
        /// <returns></returns>
        public static string TempPath(string subPath)
        {
            //            return FileUtil.PathCombine(AppSettingsUtil.Get("Path.Temp"), subPath) + Path.DirectorySeparatorChar;
            return PathCombine(@"C:\Temp", subPath) + Path.DirectorySeparatorChar;
        }
        #endregion

        #region TempPathKnownFile
        /// <summary>
        /// Generates the full path for your supplied filename at the temporary directory location.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        public static string TempPathKnownFile(string fileName)
        {
            return PathCombine(TempPath(), fileName);
        }

        /// <summary>
        /// Generates the full path for your supplied filename at the temporary directory and sub-directory location.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="subPath">The sub path.</param>
        /// <returns></returns>
        public static string TempPathKnownFile(string fileName, string subPath)
        {
            return PathCombine(TempPath(subPath), fileName);
        }
        #endregion

        #region TempFileName
        /// <summary>
        /// Generate a temporary filename (Uses a GUID).
        /// </summary>
        /// <returns></returns>
        public static string TempFileName()
        {
            return RndUtil.GuidValue("N");
        }

        /// <summary>
        /// Generate a temporary filename (Uses a GUID), you can give it an extension in dot notation. eg. (.XML, .TXT).
        /// </summary>
        /// <param name="fileExtension">The file extension.</param>
        /// <returns></returns>
        public static string TempFileName(string fileExtension)
        {
            return RndUtil.GuidValue("N");
        }

        /// <summary>
        /// Generate a temporary filename (Uses a GUID), you can give it a prefix and an extension in dot notation. eg. (.XML, .TXT).
        /// </summary>
        /// <example>
        /// TempFileName("20080616", ".PDF") returns a file name 20080616_{GuidGoesHere}.PDF
        /// </example>
        /// <param name="fileExtension">The file extension.</param>
        /// <param name="filePrefix">The file prefix.</param>
        /// <returns></returns>
        public static string TempFileName(string fileExtension, string filePrefix)
        {
            return filePrefix + "_" + RndUtil.GuidValue("N") + fileExtension;
        }
        #endregion

        #region TempPathTempFile
        /// <summary>
        /// Temps the path temp file.
        /// </summary>
        /// <returns></returns>
        public static string TempPathTempFile()
        {
            return PathCombine(TempPath(), TempFileName());
        }

        /// <summary>
        /// Temps the path temp file.
        /// </summary>
        /// <param name="subPath">The sub path.</param>
        /// <param name="fileExtension">The file extension.</param>
        /// <param name="filePrefix">The file prefix.</param>
        /// <returns></returns>
        public static string TempPathTempFile(string subPath, string fileExtension, string filePrefix)
        {
            return PathCombine(TempPath(subPath), TempFileName(fileExtension, filePrefix));
        }
        #endregion

        #region ClassToPath
        public static string ClassNamespaceToPath(object classInstance)
        {
            return classInstance.GetType().Namespace.Replace('.', '\\');
        }

        /// <summary>
        /// Returns a path that represents the full namespace of a class. The path is prefixed with a root path.
        /// </summary>
        /// <param name="classInstance">The class instance.</param>
        /// <param name="rootPath">The root path.</param>
        /// <returns></returns>
        public static string ClassNamespaceToPath(object classInstance, string rootPath)
        {
            return PathCombineAsDirectory(rootPath, ClassNamespaceToPath(classInstance));
        }
        #endregion

        #region FolderSeperatorsForWeb
        public static string FolderSeperatorsForWeb(string path)
        {
            return path == null ? string.Empty : path.Replace('\\', '/');
        }
        #endregion

        #region FolderSeperatorsForFileSystem
        public static string FolderSeperatorsForFileSystem(string path)
        {
            //return path == null ? string.Empty : path.Replace('\\', '/');// Looks wrong (DAVE)
            return path == null ? string.Empty : path.Replace('/', '\\');
        }
        #endregion

        #region GetFiles
        /// <summary>
        /// Return a list of files from specified directory, Defaults to TopDirectoryOnly
        /// </summary>
        /// <param name="directoryPath">DirectoryPath</param>
        /// <param name="filePattern">File Pattern eg. *.*</param>
        /// <returns>Array of FileInfo objects</returns>
        public static FileInfo[] GetFiles(string directoryPath, string filePattern)
        {
            return GetFiles(directoryPath, filePattern, SearchOption.TopDirectoryOnly);
        }

        /// <summary>
        /// Return a list of files from specified directory
        /// </summary>
        /// <param name="directoryPath">DirectoryPath</param>
        /// <param name="filePattern">File Pattern eg. *.*</param>
        /// <param name="searchOptions">Search Options</param>
        /// <returns>Array of FileInfo objects</returns>
        public static FileInfo[] GetFiles(string directoryPath, string filePattern, SearchOption searchOptions)
        {
            var directory = new DirectoryInfo(directoryPath);

            return directory.GetFiles(filePattern, SearchOption.TopDirectoryOnly);
        }
        #endregion

        #region GetFolders
        /// <summary>
        /// Return a list of sub directories from specified directory
        /// </summary>
        /// <param name="directoryPath">DirectoryPath</param>
        /// <returns>Array of FileInfo objects</returns>
        public static List<DirectoryInfo> GetFolders(string directoryPath)
        {
            var directory = new DirectoryInfo(directoryPath);

            var subFolders = directory.GetDirectories();

            var result = new List<DirectoryInfo>();

            foreach (var subFolder in subFolders)
            {
                if (!subFolder.Attributes.EnumHas(FileAttributes.Hidden) &&
                    !subFolder.Attributes.EnumHas(FileAttributes.System))
                {
                    result.Add(subFolder);
                }
            }

            return result;
        }
        #endregion

        #region RemoveExtension
        public static string RemoveExtension(string fileName, string extension)
        {
            if (Is.EmptyString(fileName) || Is.EmptyString(extension))
            {
                return fileName;
            }

            if (extension.StartsWith("."))
            {
                if (fileName.EndsWith(extension, FrameworkConstant.DEFAULT_COMPARISON_IGNORECASE))
                {
                    return StrUtil.LeftOf(fileName, fileName.Length - extension.Length);
                }
            }
            else
            {
                if (fileName.EndsWith("." + extension, FrameworkConstant.DEFAULT_COMPARISON_IGNORECASE))
                {
                    return StrUtil.LeftOf(fileName, fileName.Length - extension.Length + 1);
                }
            }
            return fileName;
        }
        #endregion

        #region Copy
        /// <summary>
        /// Copy a file. Defaults to overwrite = false
        /// </summary>
        /// <remarks>Currently facades through to File.Copy, future extendable</remarks>
        /// <param name="sourceFileName"></param>
        /// <param name="destFileName"></param>
        public static void Copy(string sourceFileName, string destFileName)
        {
            File.Copy(sourceFileName, destFileName, false);
        }
        /// <summary>
        /// Copy a file
        /// </summary>
        /// <remarks>Currently facades through to File.Copy, future extendable</remarks>
        /// <param name="sourceFileName"></param>
        /// <param name="destFileName"></param>
        /// <param name="overwrite"></param>
        public static void Copy(string sourceFileName, string destFileName, bool overwrite)
        {
            File.Copy(sourceFileName, destFileName, overwrite);
        }

        /// <summary>
        /// Copies if different.
        /// </summary>
        /// <param name="sourceFileName">Name of the source file.</param>
        /// <param name="destFileName">Name of the dest file.</param>
        /// <returns>True if file copied, false if not copied</returns>
        public static bool CopyIfDifferent(string sourceFileName, string destFileName)
        {
            if (File.Exists(sourceFileName) && File.Exists(destFileName))
            {
                var infoSource = new FileInfo(sourceFileName);
                var infoDest = new FileInfo(destFileName);

                if (infoSource.LastWriteTime > infoDest.LastWriteTime)
                {
                    File.Copy(sourceFileName, destFileName, true);

                    return true;
                }
            }
            else
            {
                File.Copy(sourceFileName, destFileName, true);


                return true;
            }

            return false;

        }
        #endregion

        #region CopyDirectory
        /// <summary>
        /// Directories the copy.
        /// </summary>
        /// <param name="log">The log type to use.</param>
        /// <param name="sourceFolder">The source folder.</param>
        /// <param name="destFolder">The dest folder.</param>
        /// <param name="copySubDirs">if set to <c>true</c> [copy sub dirs].</param>
        /// <param name="searchPattern">The search pattern.</param>
        /// <param name="overwrite">if set to <c>true</c> [overwrite].</param>
        /// <param name="copyIfDifferent">if set to <c>true</c> [copy if different].</param>
        /// <exception cref="System.IO.DirectoryNotFoundException">Source directory does not exist or could not be found:  + sourceFolder</exception>
        public static void CopyDirectory(string sourceFolder, string destFolder, bool copySubDirs, string searchPattern = "*", bool overwrite = false, bool copyIfDifferent = false)
        {
            CopyDirectory(CopyDirectoryLog.None, sourceFolder, destFolder, copySubDirs, searchPattern, overwrite, copyIfDifferent);
        }

        /// <summary>
        /// Directories the copy.
        /// </summary>
        /// <param name="log">The log type to use.</param>
        /// <param name="sourceFolder">The source folder.</param>
        /// <param name="destFolder">The dest folder.</param>
        /// <param name="copySubDirs">if set to <c>true</c> [copy sub dirs].</param>
        /// <param name="searchPattern">The search pattern.</param>
        /// <param name="overwrite">if set to <c>true</c> [overwrite].</param>
        /// <param name="copyIfDifferent">if set to <c>true</c> [copy if different].</param>
        /// <exception cref="System.IO.DirectoryNotFoundException">Source directory does not exist or could not be found:  + sourceFolder</exception>
        public static void CopyDirectory(CopyDirectoryLog log, string sourceFolder, string destFolder, bool copySubDirs, string searchPattern = "*", bool overwrite = false, bool copyIfDifferent = false)
        {
            var dir = new DirectoryInfo(sourceFolder);
            var dirs = dir.GetDirectories();

            // If the source directory does not exist, throw an exception.
            if (!dir.Exists)
            {
                throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceFolder);
            }

            // If the destination directory does not exist, create it.
            if (!Directory.Exists(destFolder))
            {
                Directory.CreateDirectory(destFolder);
            }


            // Get the file contents of the directory to copy.
            var files = dir.GetFiles(searchPattern);

            foreach (var file in files)
            {
                var targetFileName = Path.Combine(destFolder, file.Name);

                if (copyIfDifferent)
                {
                    var sourceFileName = file.FullName;

                    if (CopyIfDifferent(sourceFileName, targetFileName))
                    {
                        if (log == CopyDirectoryLog.PrintCopiedFileName)
                        {
                            _.P(targetFileName);
                        }
                    }
                }
                else
                {
                    // Copy the file.
                    file.CopyTo(targetFileName, overwrite);

                    if (log == CopyDirectoryLog.PrintCopiedFileName)
                    {
                        _.P(targetFileName);
                    }
                }
            }

            // If copySubDirs is true, copy the subdirectories.
            if (!copySubDirs)
            {
                return;
            }

            // Recursively call self
            foreach (var subdir in dirs)
            {
                // Create the subdirectory.
                var targetPath = Path.Combine(destFolder, subdir.Name);

                // Copy the subdirectories.
                CopyDirectory(log, subdir.FullName, targetPath, true, searchPattern, overwrite, copyIfDifferent);
            }
        }
        #endregion

        public delegate void RecurseFilesDelegate<T>(RecurseFileEvent<T> context);

        #region RecurseFiles
        public static void RecurseFiles<TEventData>(string folder, RecurseFileEvent<TEventData> context, string searchPattern = "*")
        {
            var dir = new DirectoryInfo(folder);

            // If the directory does not exist, throw an exception.
            if (!dir.Exists)
            {
                throw new DirectoryNotFoundException("Directory does not exist or could not be found: " + folder);
            }

            context.SetPath(dir);
            context.Action(context);

            // Get the file contents of the directory to copy.
            var files = dir.GetFiles(searchPattern);

            foreach (var file in files)
            {
                context.SetPath(file);
                context.Action(context);
            }

            var dirs = dir.GetDirectories();

            // Recursively call self
            foreach (var subdir in dirs)
            {
                RecurseFiles(subdir.FullName, context, searchPattern);
            }
        }
        #endregion

        #region Delete
        public static void Delete(string path)
        {
            Delete(new FileInfo(path));
        }
        public static void Delete(string path, IsDeletePredicate isDelete)
        {
            Delete(new FileInfo(path), isDelete);
        }

        public static void Delete(string path, string filePattern)
        {
            var files = GetFiles(path, filePattern);

            Delete(files);
        }
        public static void Delete(string path, string filePattern, IsDeletePredicate isDelete)
        {
            var files = GetFiles(path, filePattern);

            Delete(files, isDelete);
        }

        private static void Delete(FileInfo file)
        {
            if (file != null && file.Exists)
            {
                file.Delete();
            }
        }

        private static void Delete(FileInfo file, IsDeletePredicate isDelete)
        {
            if (file != null && file.Exists && isDelete(file))
            {
                file.Delete();
            }
        }
        public static void Delete(IEnumerable<FileInfo> files)
        {
            foreach (var file in files)
            {
                Delete(file);
            }
        }

        public delegate bool IsDeletePredicate(FileInfo file);

        public static void Delete(IEnumerable<FileInfo> files, IsDeletePredicate isDelete)
        {
            foreach (var file in files)
            {
                Delete(file, isDelete);
            }
        }

        #endregion

        #region DeleteDirectory
        public static void DeleteDirectory(string path)
        {
            DeleteDirectory(new DirectoryInfo(path), true);
        }
        public static void DeleteDirectory(string path, bool recursive)
        {
            DeleteDirectory(new DirectoryInfo(path), recursive);
        }
        public static void DeleteDirectory(DirectoryInfo directory)
        {
            DeleteDirectory(directory, true);
        }

        public static void DeleteDirectory(DirectoryInfo directory, bool recursive)
        {
            if (directory != null && directory.Exists)
            {
                directory.Delete(recursive);
            }
        }
        #endregion

        #region ForceDelete
        public static void ForceDelete(FileInfo file)
        {
            if (file != null && file.Exists)
            {
                if ((file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                {
                    file.Attributes -= FileAttributes.ReadOnly;
                }
                if ((file.Attributes & FileAttributes.System) == FileAttributes.System)
                {
                    file.Attributes -= FileAttributes.System;
                }
                if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                {
                    file.Attributes -= FileAttributes.Hidden;
                }
                //Logger.Debug("delete file: " + file.Name);
                file.Delete();
            }
        }
        #endregion

        #region ForceDeleteTree
        public static void ForceDeleteTree(string path)
        {
            DirectoryInfo dir = new DirectoryInfo(path);

            ForceDeleteTree(dir);
        }

        public static void ForceDeleteTree(DirectoryInfo dir)
        {
            if (dir != null && dir.Exists)
            {
                foreach (FileSystemInfo fs in dir.GetFileSystemInfos())
                {
                    ForceDelete(fs as FileInfo);
                    ForceDeleteTree(fs as DirectoryInfo);
                }
                //Logger.Debug("delete directory: " + dir.Name);
                dir.Delete();
            }
        }
        #endregion

        #region FileChangeWatcher
        public static FileSystemWatcher FileChangeWatcher(string fileName, FileSystemEventHandler changedEventHandler)
        {
            return FileChangeWatcher(fileName, changedEventHandler, true);
        }

        public static FileSystemWatcher FileChangeWatcher(string fileName, FileSystemEventHandler changedEventHandler, bool enableEvents)
        {
            var fPath = Path.GetDirectoryName(fileName);
            var fName = Path.GetFileName(fileName);

            return FileChangeWatcher(fPath, fName, changedEventHandler, enableEvents);
        }
        public static FileSystemWatcher FileChangeWatcher(string filePath, string filter, FileSystemEventHandler changedEventHandler, bool enableEvents)
        {
            var result = new FileSystemWatcher(filePath, filter);

            result.Changed += changedEventHandler;
            result.NotifyFilter = NotifyFilters.LastWrite;
            result.EnableRaisingEvents = enableEvents;

            return result;
        }
        #endregion

        #region WaitForFileProcessUnlock

        public static void WaitForFileProcessUnlock(string fileName, int noOfTries = 10, int millisecondsTimeout = 150)
        {
            var tryCount = 0;

            while (tryCount++ < noOfTries)
            {
                //_.P("TryCount: "+ tryCount);
                try
                {
                    var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None);

                    fs.Close();

                    tryCount = noOfTries;
                }
                catch (IOException ioe)
                {
                    if (ioe.Message.StartsWith("The process cannot access the file"))
                    {
                        //Logger.Debug("ProcessLocked for file: " + fileName + ", Attempt (" + tryCount + " / " + noOfTries + ")");

                        Thread.Sleep(millisecondsTimeout);
                    }
                    else
                    {
                        // Legitimate Exception - StopTrying
                        _.Exception(ioe);
                        throw;
                    }
                }
                catch (Exception ex)
                {
                    _.Exception(ex);
                    throw;
                }
            }
        }
        #endregion

        #region CheckInvalidPathChars

        /// <summary>
        /// Checks if path contains invalid chars.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns></returns>
        public static bool CheckInvalidPathChars(string path)
        {
            if (path == null)
            {
                // Changed by Dave on 18 March 2014 from true to false, null will be treated as empty
                //return true;
                return false;
            }

            for (var i = 0; i < path.Length; i++)
            {
                int c = path[i];
                if (((c == 0x22) || (c == 60)) || (c == 63) || (((c == 0x3e) || (c == 0x7c)) || (c < 0x20)))
                {
                    return true;
                }
            }
            return false;
        }
        #endregion

        #region CheckValidPathChars

        /// <summary>
        /// Checks if path NOT contains invalid chars.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns></returns>
        public static bool CheckValidPathChars(string path)
        {
            return !CheckInvalidPathChars(path);
        }
        #endregion

        #region IsAttribute
        public static bool IsAttribute(string fileName, FileAttributes attribute)
        {
            return IsAttribute(new FileInfo(fileName), attribute);
        }
        public static bool IsAttribute(FileInfo fileInfo, FileAttributes attribute)
        {
            return (fileInfo.Attributes & attribute) == attribute;
        }
        #endregion

        #region SetAttribute
        public static void SetAttribute(string fileName, FileAttributes attribute)
        {
            SetAttribute(new FileInfo(fileName), attribute, true);
        }

        public static void SetAttribute(string fileName, FileAttributes attribute, bool isOn)
        {
            SetAttribute(new FileInfo(fileName), attribute, isOn);
        }
        public static void SetAttribute(FileInfo fileInfo, FileAttributes attribute)
        {
            SetAttribute(fileInfo, attribute, true);
        }
        public static void SetAttribute(FileInfo fileInfo, FileAttributes attribute, bool isOn)
        {
            if (isOn)
            {
                fileInfo.Attributes = fileInfo.Attributes | attribute;
            }
            else
            {
                if ((fileInfo.Attributes & attribute) == attribute)
                {
                    fileInfo.Attributes = fileInfo.Attributes ^ attribute;
                }
            }
        }
        #endregion

        /// <summary>
        /// Determines whether the specified file OR folder exists.
        /// </summary>
        /// <param name="path">The file to check.</param>
        /// <param name="isPathDirectory">if set to <c>true</c> [The path to check is a folder or directory].</param>
        /// <returns>
        /// true if the caller has the required permissions and <paramref name="path" /> contains the name of an existing file; otherwise, false. This method also returns false if <paramref name="path" /> is null, an invalid path, or a zero-length string. If the caller does not have sufficient permissions to read the specified file, no exception is thrown and the method returns false regardless of the existence of <paramref name="path" />.
        /// </returns>
        public static bool Exists(string path, bool isPathDirectory = false)
        {
            if (isPathDirectory)
            {
                var dir = new DirectoryInfo(path);

                return dir.Exists;
            }
            return File.Exists(path);
        }

        /// <summary>
        /// Universal Resource Identifier is is file.
        /// </summary>
        /// <param name="uri">The Universal Resource Identifier</param>
        /// <returns></returns>
        public static bool UriIsFile(string uri)
        {
            var u = GetUri(uri);

            return u != null && u.IsFile;
        }

        /// <summary>
        /// Gets the URI.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <returns></returns>
        public static Uri GetUri(string uri)
        {
            Uri result;

            Uri.TryCreate(uri, UriKind.RelativeOrAbsolute, out result);

            return result;
        }

        /// <summary>
        /// Renames the file.
        /// </summary>
        /// <param name="sourceFileName">Name of the source file.</param>
        /// <param name="destinationFileName">Name of the destination file.</param>
        public static void RenameFile(string sourceFileName, string destinationFileName)
        {
            File.Move(sourceFileName, destinationFileName);
        }
    }
}